/*#include <math.h>

#include <model3d.h>
#include <scn.h>
#include <vector3.h>
#include <matrix4x4.h>
#include <transforms.h>
#include <render.h>

#include <visual.h>
#include <debug.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include <camera.h>
#include <scene.h>
#include <sphere.h>
#include <color.h>
#include <object.h>
#include <light.h>

#include <linkedlist.h>
#include <limits.h>

#include <matrix4x4.h>


struct object_node{
  listnode_t node;
  object_t *object;
};

#define sqr( a ) pow2( a )
#define vector3_sqr( v ) vector3_dot( v, v )
#define vector3_distance( P1, P2 ) sqrt ( vector3_sqr( vector3_sub( P1, P2 ) ) )

*/

#include <raytrace.h>

int sphere_intersect( vector3_t p0, vector3_t normal, sphere_t *sphere, vector3_t *Q, vector3_t *_Q ){
  vector3_t P1,P2;

  assert( sphere != NULL );

  double A,B,C;

  A = vector3_sqr( normal );
  B = 2* vector3_dot( normal, vector3_sub( p0, sphere->center ) );
  C = vector3_sqr( sphere->center) + vector3_sqr( p0 ) - 2 * vector3_dot( sphere->center, p0 ) - sqr( sphere->radius );

  double t;
  double d1,d2;

  double bas = sqr( B ) - 4 * A * C;

  if ( bas < 0 ) return 0;

  t = (double)( -B + sqrt( bas ) ) /  (double)(2 * A );

  if ( t < 0 ) return 0;

  P1 = p0;

  VECTOR3_X( P1 ) += t * VECTOR3_X( normal );
  VECTOR3_Y( P1 ) += t * VECTOR3_Y( normal );
  VECTOR3_Z( P1 ) += t * VECTOR3_Z( normal );

  if( double_is_equal( bas, 0.0, DOUBLE_PRECISION ) ){
    *Q = P1;
    return 1;
  }

  d1 = vector3_sqr( vector3_sub( p0, P1 ) );
  d1 = sqrt( d1 );

  t = (double)( -B - sqrt( sqr( B ) - 4 * A * C ) ) / (double)(2 * A );

  if ( t < 0 ) {
    if( !double_is_equal( t, 0.0, DOUBLE_PRECISION ) ){
      *Q = P1;
      return 1;
    }
  }

  P2 = p0;
  VECTOR3_X( P2 ) += t * VECTOR3_X( normal );
  VECTOR3_Y( P2 ) += t * VECTOR3_Y( normal );
  VECTOR3_Z( P2 ) += t * VECTOR3_Z( normal );

  d2 = vector3_sqr( vector3_sub( p0, P2 ) );
  d2 = sqrt( d2 );

  *Q = ( d1 < d2 ) ? P1 : P2;
  *_Q = ( d1 < d2 ) ? P2 : P1;

  return 2;
}

#define COLOR_SHADE( color, z ) (color).a *= z; (color).r *= z; (color).g *= z; (color).b *= z;

static inline
color_t reflex( color_t color1, color_t color2, double reflexion ){
  color_t color;

  color.a = color1.a + ( reflexion ) * color2.a;
  color.r = color1.r + ( reflexion ) * color2.r;
  color.g = color1.g + ( reflexion ) * color2.g;
  color.b = color1.b + ( reflexion ) * color2.b;

  return color;
}

double sphere_distance( vector3_t p, sphere_t * sphere ){
  vector3_t V;

  assert( sphere != NULL );

  V = vector3_sub( p, sphere->center );
  V = vector3_normalize( V );

  vector3_t Q, _Q;
  sphere_intersect( p, V, sphere, &Q, &_Q );

  double d;
  d = vector3_sqr( vector3_sub( p, Q ) );
  d = sqrt( d );

  return d;
}

void scene_raycast( scene_t *scene, camera_t camera ){

  sphere_t *sphere = NULL;

  struct object_node *node;

  assert( scene != NULL );

  int i,j;

  vector3_t P0;

  P0 = vector3_add( camera.center, vector3_multiply_scalar( camera.normal, - camera.distance ) );

  for( i = 0;  i < camera.surface->size.x ; i++ ){
    for( j= 0;  j < camera.surface->size.y ; j++ ){

      vector3_t P1 = camera.center;

      VECTOR3_X( P1 ) = i - (camera.surface->size.x / 2);
      VECTOR3_Y( P1 ) = j - (camera.surface->size.y / 2);

      vector3_t V;

      V = vector3_sub( P1, P0 );
      V = vector3_normalize( V );

      int hit = 0;
      double closer = LONG_MAX; // barbieri, confira se esse valor no limits.h eh o valor para double ...
      sphere_t *s;

      for_each_list_element( scene->objects, node ){
	sphere = ( sphere_t * ) node->object;

	vector3_t Q,_Q;
	int intersection = sphere_intersect( P0, V, sphere, &Q, &_Q );

	if( intersection ){
	  double d;

	  d = vector3_dot( vector3_sub( P0, Q ), vector3_sub( P0, Q ) );
	  d = sqrt( d );

	  if ( d < closer ){
	    closer = d;
	    hit = 1;
	    s = sphere;
	  }

	}

	if ( hit ){
	  double min = sphere_distance( P0, s );
	  double max;
	  color_t color;

	  max = vector3_sqr( vector3_sub( P0, s->center ) );
	  max = sqrt( max );

	  color = s->obj.color;
	  double shade =  (( max - closer ) / ( max - min ));
	  COLOR_SHADE( color, shade );
	  PIXEL_AT( camera.surface, i, j ) = color;
	}

      }

    }
  }

  return;
}

/*

http://en.wikipedia.org/wiki/Ray_tracing

For each pixel in image {
  Create ray from eyepoint passing through this pixel
  Initialize NearestT to INFINITY and NearestObject to NULL

  For every object in scene {
     If ray intersects this object {
        If t of intersection is less than NearestT {
           Set NearestT to t of the intersection
           Set NearestObject to this object
        }
     }
  }

  If NearestObject is NULL {
     Fill this pixel with background color
  } Else {
     Shoot a ray to each light source to check if in shadow
     If surface is reflective, generate reflection ray: recurse
     If transparent, generate refraction ray: recurse
     Use NearestObject and NearestT to compute shading function
     Fill this pixel with color result of shading function
  }
}
*/


static inline
vector3_t snell( vector3_t I, vector3_t N ){
  vector3_t R;
  R = vector3_cross( N, I );
  R = vector3_cross( N, R );
  R = vector3_multiply_scalar( R, - 2 );
  R = vector3_add( I, R );
  R = vector3_normalize( R );
  return R;
}

static inline
color_t backward_raytrace( scene_t *scene, vector3_t P, vector3_t V, linkedlist_t *ilumination, int depth ){
  double closest = LONG_MAX;
  vector3_t Q,_Q;
  sphere_t *s;
  int intersects = 0;
  vector3_t T,_T;
  sphere_t *sphere;
  int hit;

  double intensity = 0;;

  light_t *light;

  //char *str;

  color_t background;
  COLOR_FROM_ARGB( background, COLOR_BLACK );

  color_t reflected;
  COLOR_FROM_ARGB( reflected, COLOR_BLACK );

  color_t refracted;
  COLOR_FROM_ARGB( refracted, COLOR_BLACK );


  assert( scene != NULL );

  // finds intersection point
  struct object_node *node;
  for_each_list_element( scene->objects, node ){
    sphere = ( sphere_t * ) node->object;
    hit = sphere_intersect( P, V, sphere , &T, &_T );

    if ( ! hit ) continue;
    if ( vector3_is_equal( P, T ) ) {
      hit = 0;
      continue;
    }

    double d = vector3_distance( P, T  );

    if ( d < closest ){
      closest = d;
      Q = T;
      _Q = _T;
      intersects = 1;
      s = sphere;
    }

  }

  if ( ! intersects ) return background;

  if ( depth > 0 ){
    vector3_t R,N;

    // reflexion ray R = v - 2 * n * ( n * v )
#ifdef _DEBUG
    str = vector3_str( V );
    printf("incident ray direction %s\n",str);
    free( str );
#endif

    N = vector3_sub( Q, s->center );
    N = vector3_normalize( N );

#ifdef _DEBUG
    str = vector3_str( N );
    printf("normal %s\n",str);
    free( str );
#endif

    R = snell( vector3_multiply_scalar( V, -1 ), N );

#ifdef _DEBUG
    str = vector3_str( R );
    printf("reflexion ray direction %s\n",str);
    free( str );
#endif

    reflected = backward_raytrace( scene, Q, R, ilumination, depth - 1 );
  }

  if ( depth > 0 ){
    if ( ! vector3_is_equal( Q, _Q ) )
      refracted = backward_raytrace( scene, _Q, V, ilumination, depth - 1 );
  }

  struct object_node *lnode;
   for_each_list_element( ilumination, lnode ){
    light = (light_t*)lnode->object;

    //char *str;
    //str = light_str( light );
    //printf("hi %s\n", str );
    //free( str );

    // calculates intersection point -> light source direction
    vector3_t LV;
    LV = vector3_sub( light->center, Q );
    LV = vector3_normalize( LV );

    // searchs for objects on the way
    hit = 0;
    for_each_list_element( scene->objects, node ){
      sphere = ( sphere_t * ) node->object;
      hit = sphere_intersect( Q, LV, sphere , &T, &_T );

      if ( hit == 1 ) hit = 0;
      if ( hit )  break;
    }

    // if there is a clear path, then we can color it
    if( ! hit ){
      vector3_t N;
      //printf("I can see the ligth!\n");
      N = vector3_sub( Q, s->center );
      N = vector3_normalize( N );

      vector3_t R = snell( vector3_multiply_scalar( LV, -1 ), N );
      R =vector3_normalize( R );

      /*
      str = vector3_str( N );
      printf("N = %s\n",str);
      free( str );

      str = vector3_str( vector3_multiply_scalar(LV, - 1) );
      printf("LV = %s\n",str);
      free( str );

      str = vector3_str( R );
      printf("R = %s\n",str);
      free( str );
      */
      double phong = s->obj.ks * pow( vector3_dot( R, V ), s->obj.nphong );
      double lamberts = s->obj.kd * cos( vector3_angle( N, LV ) );

      //printf("phong = %f lamberts = %f\n", phong, lamberts );

      //color_t color = s->obj.color;
      //double shade = lamberts + phong; // ambient + lamberts + phongs

      intensity += (double ) (lamberts + phong) / (double)2;
      //COLOR_REFLEX( color, reflexion, 0.7 );
      //COLOR_SHADE( color, shade );
      //color = reflex( color, reflected, 0.7 );
      //return color;
    }

   }

#ifdef _DEBUG

   printf("reflected b = %d\n", reflected.b );
   //assert( reflected.g == 0 );
#endif

   intensity += 0.1; // ambient
   color_t color = s->obj.color;
   COLOR_SHADE( color, intensity );
   color = reflex( color, reflected, ( 1 - s->obj.transp ) );
   color = reflex( color, refracted, (     s->obj.transp ) );
   return color;


  //return background;
}

//void scene_raytrace( scene_t *scene, camera_t camera, light_t *light ){
void scene_raytrace( scene_t *scene, camera_t camera, linkedlist_t *ilumination ){

  assert( scene != NULL );

  int i,j;
  vector3_t P0;

  P0 = vector3_add( camera.center, vector3_multiply_scalar( camera.normal, - camera.distance ) );

  matrix4x4_t *m_rotx = rotate_x( camera.rotx );
  matrix4x4_t *m_roty = rotate_y( camera.roty );
  matrix4x4_t *pipeline[3] = { m_rotx, m_roty, NULL };
  matrix4x4_t *rot = compile_pipeline( pipeline );

  multiply_matrix4x4_vector3( *rot, P0, &P0 );

  //for each pixel no the camera
  for( i = 0;  i < camera.surface->size.x ; i++ ){
    for( j= 0;  j < camera.surface->size.y ; j++ ){

      vector3_t P1 = camera.center;

      VECTOR3_X( P1 ) = i - (camera.surface->size.x / 2);
      VECTOR3_Y( P1 ) = j - (camera.surface->size.y / 2);

      multiply_matrix4x4_vector3( *rot, P1, &P1 );

#ifdef _DEBUG

      i = 0 + camera.surface->size.x / 2;
      j = 0 + camera.surface->size.y / 2;

      VECTOR3_X( P1 ) = i - (camera.surface->size.x / 2);
      VECTOR3_Y( P1 ) = j - (camera.surface->size.y / 2);

      multiply_matrix4x4_vector3( *rot, P1, &P1 );

#endif

      //printf("%d %d\n",i,j);

      vector3_t V;

      V = vector3_sub( P1, P0 );
      V = vector3_normalize( V );

      PIXEL_AT( camera.surface, i, j ) = backward_raytrace( scene, P0, V, ilumination, 1 );

#ifdef _DEBUG

      return;

#endif

    }
  }

  return;

}

